// 数据代理，vue原代码用的是proxyGetter名字的函数，数据劫持，名字ReactiveGetter
//**Object.defineProperty只能劫持已经存在的属性
//注意：Dep实例化时，是给每一个属性绑定的dep，所以dep中收集的watcher都是来自同一个数据的watcher（当同一个数据多次在模板中用插值语法获取时就有多个watcher）
//注意：vue原生方法，是把watcher存在栈空间中的
//第十一步：定义保存Watcher的全局变量
let W = null 

//第十步：封装Watcher类
//Watcher类其实就是发布订阅模式中的订阅者,只有模板解析的时候用到了属性,才会创建一个订阅者(每一个模板的数据对应一个订阅者watcher)
class Watcher{
    //插值语法的本质是实例watcher类，并传入用到的属性名，以及这个属性所处的对象,Watcher实例上有一个update方法，会进行模板编译等更新视图的工作，因为在Dep中需要调用这个update方法，为了方便dep中也能拿到当前Watcher实例，所以要定义一个全局变量，一旦实例化Watcher了，就把this赋值给这个全局变量
    constructor(obj,key){
        this.obj = obj 
        this.key = key
        W = this
        //调用Watcher原型上的get方法，get方法中去获取值，这样就会进到数据劫持的get方法中获取到值
        this.get()
        //获取到值之后，需要把全局变量W重新变为null，因为如果下次不是通过插值语法获取值时，会进到数据劫持的get中，发现W存在，又会重新收集一次watcher，所以要把W变为null,其他数据会重新创建新的实例覆盖W，所以没关系
        W=null

    }
    get(){
    
        return this.obj[this.key]
    }
    //给wacther设置一个方法,当watcher调用这个方法的时候,就能重新去获取新的数据,并重新渲染模板(Compiler)
    update(){
        //update中的详细逻辑没有写出来
        console.log(
            `${
              this.key
            }的数据发生更新了,我们重新获取一下吧${this.get()},并重新渲染一下视图`
          );
    
    }



}




//第六步：创建Dep类,给dep实例上添加空数组，并在原型上定义两个方法，一个方法用来给空数组中增加依赖，一个方法用来调用watcher上的update方法更新视图
//Dep就是发布订阅模型中的订阅器（帮助订阅者更新视图的工具）,拥有两个方法,收集所有依赖,通知所有依赖
class Dep{
    //只要实例化dep，就给实例对象上添一个空数组，储存依赖（watcher）
    constructor(){
        this.subs=[]
    }
    //这个方法需要在进入数据劫持的get函数中调用
    depend(watcher){
        this.subs.push(watcher)
        console.log(
            "每次访问我的数据的时候,我就把watcher收集到dep.subs这个数组中",
            this.subs
          );
    }
    //这个方法需要在数据劫持的set函数中调用，每次更新值时，调用这个方法，自动帮我们更新视图
    notify(){
        console.log("通知所有的watcher 重新加载新的数据");
        //在这个方法中需要遍历subs中的watcher，并调用watcher上的update方法
        this.subs.forEach((watcher)=>{
         watcher.update()
        })
    }


}


//第五步：封装defineReactive函数
function defineReactive(_data,key,value){
    //第一次传入进行响应式操作的data是vm传的data对象，但如果data对象属性的值也是对象的话，也要进行响应式操作
    //所以在响应式操作拿到第一次通过vm传入的data对象中属性的值value后，要再传入observe函数中进行判断
    observe(value)
    //第七步：在对传进来的属性进行劫持的时候，都要为每一个属性创建一个Dep实例，用来收集依赖（watcher）和通知依赖（update）
    const dep =  new Dep()
    //响应式操作主要是希望我们获取_data上的值时，不是直接获取值，而是通过_data上的get函数中拿值
    Object.defineProperty(_data,key,{
        get:function ReactiveGetter(){
            console.log("正在获取" + key + "的值", "收集所有依赖项watcher");
            //第八步：在获取值进入到get函数中拿到值之前，要调用dep中的depend方法，把这个数据的watcher放到dep上的subs数组中，用于之后数据修改后，自动通知视图更新
            //因为获取数据的方式有两种，一种是模板中插值语法获取值，另一种是打印获取值，但只有在插值语法中使用时才会创建watcher，所以，要做一个判断，如果有watcher就调用denpend方法，没有就直接返回数据的值
            if(W){
                dep.depend(W)
            }
            return value
        },
        set:function ReactiveSetter(newVal){
            if (newVal === value) return;
            console.log("正在设置" + key + "的值", "通知所有的watcher进行更新");
            //注意：**这里赋值是一定要把新的newValue赋值给传过来的value值，如果赋值给_data[key]，就会造成死循环，因为代表一直在给数据中的属性设置值
            value=newVal
            //第九步：在修改好数据之后，需要调用dep中的notify方法，通知这个数据的所有watcher更新视图
            dep.notify()
        }
    })
}



//第四步：封装Observer类，发布-订阅模型中的发布者
 class Observer{
    constructor(_data){
//构造器函数,当类被实例化的时候,实例化对象会直接调用原型对象上的constructor方法
//把这个对象放在实例上,将来在任何原型方法中(原型方法中的this指向实例)都可以通过this访问到这个实例上的数据
    this._data = _data;
    //因为在observe函数中只过滤掉了基本类型值和null，所以在这里要判断是数组还是对象
    //1.如果是数组，就把数据传给遍历数组的原型方法observeArray里，在这个原型方法里遍历数组，把数组中的每一项再传入observe函数中判断数组中的每一项是否是对象
    //因为对于数组中基本类型值的修改，或者数组的长度是否改变，是不会做响应式处理的，这些数据的修改，包装了数组的方法来达到数据响应式（例如push、pop等）
    //我们只会对数组中的对象进行响应式操作
    //2.如果是对象，则把数据传给对象的原型方法walk里，遍历对象的key，并把key、_data、_data[key]传给defineReactive函数中做数据劫持
    if(Array.isArray(this._data)){
        this.observeArray(this._data)
    
    }else

        this.walk(this._data)
    }
  //给observer类的原型上添加一个observerArray方法,当数据是数组的时候,会进行下一步处理
    
    observeArray(_data){
        _data.forEach((item) => observe(item));
    }

    walk(_data){
       //当进入walk的时候,数据此时一定是一个对象,我们要对对象进行遍历,将一个个属性都传给defineReactive函数进行响应式操作
       for(let key of Object.keys(_data)) {
                defineReactive(_data,key,_data[key])
        }
    }


}





//第三步，封装observe函数，主要是将数据进行过滤后，传给Observer类的实例对象
function observe (_data){
    //基本类型值不处理
    if(typeof _data!=="object"||_data===null) return
    new Observer(_data)

}



//第二步，封装Vue构造函数，做数据代理，并把数据传给observe函数
function Vue(options){
    this._data = options.data
    for(let key of Object.keys(this._data)){
        Object.defineProperty(this,key,{
            get:function proxyGetter(){
                return this._data[key]
            
            },
            set:function proxyGetter(newVal){
                this._data[key]=newVal
            
            }
        
        
        
        })
        
    
    }
    observe(this._data)
}







//第一步：创建vm，传入数据
const vm =new Vue({
    data:{
        count: 1,
        student: {
        name: "laowang",
        age: 18,
        },
        food: [
        { id: "001", type: "热干面" },
        { id: "002", type: "烧饼" },
        ],

    
    
    }

})
  
//第十二步：模拟插值语法
//模拟模板解析,每个组件都会实例化一个watcher 然后开始观察数据
console.log(vm)
function huoqu(){
    new Watcher(vm,"count")
}
function shezhi(){
    vm.count = 4
}
huoqu()//调用获取函数，就会进到数据劫持的get函数中还有Dep中的depend中
// shezhi()//调用设置函数，就会进到数据劫持的set函数中，然后进到dep中的notify函数中，通知依赖